package synchronicity.util;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import synchronicity.io.Streamable;

/**
 * A Range encapsulates two values, left and right, that define a range of some
 * sort. The left value must be less than or equal to the right value.
 * Ranges are immutable, except for deserialization.
 */
public class Range implements Streamable {
	private long left, right;

	public Range(long left, long right) {
		if (left > right)
			throw new IllegalArgumentException("Invalid range " + left + " "
					+ right);
		this.left = left;
		this.right = right;
	}

	/**
	 * True if any part of this range overlaps with the given range.
	 */
	public boolean intersects(Range other) {
		return this.right >= other.left && this.left <= other.right;
	}

	/**
	 * Combine this range with another.
	 * @pre This range and the given range intersect 
	 */
	public Range union(Range other) {
		if (intersects(other)) {
			long newLeft, newRight;
			newLeft = left < other.getLeft() ? left : other.getLeft();
			newRight = right > other.getRight() ? right : other.getRight();
			return new Range(newLeft, newRight);
		} else {
			return null;
		}
	}

	/**
	 * Intersect the other range with this.
	 * @pre This range and the given range intersect
	 * @return The Range of values common to both ranges
	 */
	public Range intersect(Range other) {
		if (intersects(other)) {
			long newLeft, newRight;
			newLeft = left > other.getLeft() ? left : other.getLeft();
			newRight = right < other.getRight() ? right : other.getRight();
			return new Range(newLeft, newRight);
		} else {
			return null;
		}
	}

	public long getLength() {
		return right - left;
	}

	public long getLeft() {
		return left;
	}

	public long getRight() {
		return right;
	}

	/**
	 * Traditional compareTo function. Returns 0 if these ranges intersect,
	 * -1 if this range is less than the given, 1 if this range is greater
	 * than the given.
	 * Note that a value of 0 is not equivalent to the ranges being equal
	 * in the sense of the equals method.
	 */
	public int compareTo(Range other) {
		if (other.getLeft() > right) {
			return -1;
		}
		if (other.getRight() < left) {
			return 1;
		}
		return 0;
	}

	public String toString() {
		return "[" + left + " " + right + "]";
	}

	/**
	 * Return true iff the supplied range's left and right values are identical
	 * to this range's.
	 */
	public boolean equals(Object obj) {
		try {
			Range other = (Range) obj;
			if (left == other.getLeft() && right == other.getRight())
				return true;
			else
				return false;
		} catch (ClassCastException cce) {
			return false;
		}
	}

	public void read(DataInputStream stream) throws IOException {
		this.left = stream.readLong();
		this.right = stream.readLong();
	}

	public void write(DataOutputStream stream) throws IOException {
		stream.writeLong(left);
		stream.writeLong(right);
	}
}
